package com.semanticweb.webapp.dao.rbac;

import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.record.formula.functions.T;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.semanticweb.webapp.model.rbac.AbstractPersistedEntity;

/**
 * Provides default implementations for the {@link GenericDao} methods.
 * 
 * @author Vedran Cuca 19/2011
 * @param <T>
 *            Type of the persisted entity this DAO will handle
 */
public abstract class AbstractGenericHibernateDao<T extends AbstractPersistedEntity>
		implements GenericDao<T> {
	
	private static final Logger LOGGER = LoggerFactory
			.getLogger(AbstractGenericHibernateDao.class); 

	/**
	 * Path separator for hibernate aliases. Value: {@value DOT}
	 */
	protected static final String DOT = ".";
	/**
	 * Field PERCENT. (value is ""%"") Value: {@value PERCENT}
	 */
	protected static final String PERCENT = "%";

	private final Class<T> persistentClass;

	@Autowired
	private SessionFactory sessionFactory;

	/**
	 * Default constructor.
	 */
	@SuppressWarnings("unchecked")
	public AbstractGenericHibernateDao() {
		LOGGER.info("Entered in AbstractGenericHibernateDao constructor");
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * Get persistence class.
	 * 
	 * @return Class
	 */
	protected Class<T> getPersistentClass() {
		return persistentClass;
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#findById(Long)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public T findById(final Long id) {
		return (T) getCurrentSession().get(getPersistentClass(), id);
	}

	/**
	 * @param entity
	 *            T
	 */
	@Override
	public T save(final T entity) {
		getCurrentSession().save(entity);
		return entity;
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#update(AbstractPersistedEntity)
	 * @param entity
	 *            T
	 */
	@Override
	public T update(final T entity) {
		getCurrentSession().update(entity);
		return entity;
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#saveOrUpdate(AbstractPersistedEntity)
	 * @param entity
	 *            T
	 */
	@Override
	public T saveOrUpdate(final T entity) {
		getCurrentSession().saveOrUpdate(merge(entity));
		return entity;
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#merge(AbstractPersistedEntity)
	 * @param entity
	 *            T
	 * @return T
	 */
	@Override
	@SuppressWarnings("unchecked")
	public T merge(final T entity) {
		return (T) getCurrentSession().merge(entity);
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#delete(AbstractPersistedEntity)
	 * @param entity
	 *            T
	 */
	@Override
	public void delete(final T entity) {
		getCurrentSession().delete(entity);
		getCurrentSession().flush();
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#deleteById(Long)
	 * @param id
	 *            Long
	 */
	@Override
	public void deleteById(final Long id) {
		getCurrentSession().delete(findById(id));
		getCurrentSession().flush();
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDao#findAll()
	 * @return List<T>
	 */
	@Override
	public List<T> findAll() {
		return findByCriteria();
	}

	/**
	 * Use this inside subclasses as a convenience method.
	 * 
	 * @param criterion
	 *            Criterion[]
	 * @return List
	 */
	@SuppressWarnings("unchecked")
	protected List<T> findByCriteria(final Criterion... criterion) {
		final Criteria crit = getCurrentSession().createCriteria(
				getPersistentClass());
		for (final Criterion c : criterion) {
			crit.add(c);
		}
		return crit.list();
	}
	/**
	 * Use this inside subclasses as a convenience method.
	 * 
	 * @param criterion
	 *            Criterion[]
	 * @return T
	 */
	@SuppressWarnings("unchecked")
	protected T findByCriteriaUnique(final Criterion... criterion) {
		final Criteria crit = getCurrentSession().createCriteria(
				getPersistentClass());
		for (final Criterion c : criterion) {
			crit.add(c);
		}
		return (T) crit.uniqueResult();
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDaoGenericDao#findByCriteria(Map)
	 * @param criterias
	 *            Map
	 * @return List<T>
	 */
	@Override
	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(final Map<?, ?> criterias) {
		final Criteria criteria = getCurrentSession().createCriteria(
				getPersistentClass());
		criteria.add(Restrictions.allEq(criterias));
		return criteria.list();
	}

	/**
	 * @see com.digitalarchives.project.persistance.dao.GenericDaoGenericDao#findByExample(T,
	 *      String[])
	 * @param exampleInstance
	 *            T
	 * @param excludeProperty
	 *            String[]
	 * @return List<T>
	 */
	@Override
	@SuppressWarnings("unchecked")
	public List<T> findByExample(final T exampleInstance,
			final String[] excludeProperty) {
		final Criteria crit = getCurrentSession().createCriteria(
				getPersistentClass());
		final Example example = exampleCriteria(exampleInstance,
				excludeProperty);
		crit.add(example);
		return crit.list();
	}

	/**
	 * Example criteria will be generated from example instance with possibility
	 * of fine tuning using excluded properties.
	 * 
	 * @param exampleInstance
	 *            T
	 * @param excludeProperty
	 *            String[]
	 * @return Example
	 */
	private Example exampleCriteria(final T exampleInstance,
			final String[] excludeProperty) {
		final Example example = Example.create(exampleInstance).excludeZeroes()
				.enableLike(MatchMode.ANYWHERE).ignoreCase();
		for (final String exclude : excludeProperty) {
			example.excludeProperty(exclude);
		}
		return example;
	}

	@Override
	public void addExampleCriteria(final T example, final Criteria criteria) {
		addExampleCriteria(example, criteria, new String[] {});
	}

	@Override
	public void addExampleCriteria(final T example, final Criteria criteria,
			final String[] excludeProperty) {
		if (example != null) {
			criteria.add(exampleCriteria(example, excludeProperty));
		}
	}

	protected final Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	/**
	 * Surrounds parameter String likeParameter with {@value #PERCENT}.
	 * 
	 * @param likeParameter
	 *            String
	 * @return String
	 */
	protected String surroundWithPercent(final String likeParameter) {
		return PERCENT + likeParameter + PERCENT;
	}

}
